﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 
// See License.txt in the project root for license information.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Diagnostics.Eventing;
using System.Diagnostics.Tracing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace System.Threading.Actors
{
    // Vance says strictly speaking, EventSource and EventListener are supersets of ETW functionality 
    // and may not actually use ETW to write events to log files, etc.  However, using his EventSource
    // and EventListener is the only way in the .NET Framework to play in that space.  
    //
    // Implementation note: For all the calls to WriteEvent below, pass in the event ID, the
    // actor name and the partition name.  Those are necessary for filtering events later.  We strip 
    // those values out before handing back the event payload to other parts of this same file.  
    // So if you see some indices that look like they're off by two parameters, you know why.
    [EventSource( Name= "ActorEvent", Guid = ActorETWEventSource.ProviderId)]
    public class ActorETWEventSource : EventSource
    {
        private const int ADD_ASSEMBLY = 1;
        private const int ASSEMBLY_LOAD_FAILURE = 2;
        private const int ACTOR_METHOD_SIGNATURE_FAILURE = 3;
        private const int ACTOR_METHODS_LOADED = 4;
        private const int ACTOR_METHOD_COMPLETED = 5;
        private const int ACTOR_METHOD_ERROR = 6;
        private const int ASSEMBLY_HAD_NO_ACTOR_METHODS = 7;
        private const int SUBSCRIPTION_REQUEST_SENT = 8;
        private const int SUBSCRIPTION_REQUEST_RECEIVED = 9;
        private const int EVENT_PUBLISHED = 10;
        private const int EVENT_RECEIVED = 11;
        private const int DUPLICATE_ASSEMBLY_RECEIVED = 12;
        private const int ACTOR_METHODS_UNLOADED = 13;
        private const int USER_INFO_MESSAGE = 14;
        private const int USER_VERBOSE_MESSAGE = 15;
        private const int ACTOR_CREATED = 16;
        private const int ACTOR_DELETED = 17;

        public const string ProviderId = "01590C00-618D-4405-89EA-C76696A89111";

        [Event(ADD_ASSEMBLY, Level=EventLevel.Informational, Keywords=EventKeywords.None)]
        public void AddAssembly(string actorName, string partitionName, string assemblyName, string additionType) 
        { 
            if(base.IsEnabled(EventLevel.Informational, EventKeywords.None))
                WriteEvent(ADD_ASSEMBLY, actorName, partitionName, assemblyName, additionType); 
        }

        [Event(DUPLICATE_ASSEMBLY_RECEIVED, Level = EventLevel.Informational, Keywords = EventKeywords.None)]
        public void DuplicateAssemblyReceived(string actorName, string partitionName, string assemblyName)
        {
            if (base.IsEnabled(EventLevel.Informational, EventKeywords.None))
                WriteEvent(DUPLICATE_ASSEMBLY_RECEIVED, actorName, partitionName, assemblyName);
        }

        [Event(ASSEMBLY_LOAD_FAILURE, Level=EventLevel.Warning, Keywords=EventKeywords.None)]
        public void AssemblyLoadFailure(string actorName, string partitionName, string assemblyName, string exceptionMessage) 
        {
            if (base.IsEnabled(EventLevel.Warning, EventKeywords.None))
                WriteEvent(ASSEMBLY_LOAD_FAILURE, actorName, partitionName, assemblyName, exceptionMessage); 
        }

        [Event(ACTOR_METHOD_SIGNATURE_FAILURE, Level = EventLevel.Error, Keywords = EventKeywords.None)]
        public void ActorMethodSignatureFailure(string actorName, string partitionName, string assemblyName, string methodName)
        {
            if (base.IsEnabled(EventLevel.Error, EventKeywords.None))
                WriteEvent(ACTOR_METHOD_SIGNATURE_FAILURE, actorName, partitionName, assemblyName, methodName);
        }

        private static string AggregateStringList(List<string> strings)
        {
            // Construct an aggregate string from the individual method names, then forward to official method
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < strings.Count; i++)
            {
                if (i > 0) sb.Append(',');
                sb.Append(strings[i]);
            }

            return sb.ToString();
        }

        [NonEvent]
        public void ActorMethodsLoaded(string actorName, string partitionName, string assemblyName, List<string> methodNames)
        {
            if (base.IsEnabled(EventLevel.Informational, EventKeywords.None))
            {
                // Construct an aggregate string from the individual method names, then forward to official method
                var aggregatedMethodNames = AggregateStringList(methodNames);
                ActorMethodsLoaded(actorName, partitionName, assemblyName, aggregatedMethodNames);
            }
        }

        [Event(ACTOR_METHODS_LOADED, Level = EventLevel.Informational, Keywords = EventKeywords.None)]
        public void ActorMethodsLoaded(string actorName, string partitionName, string assemblyName, string methodNames)
        {
            if (base.IsEnabled(EventLevel.Informational, EventKeywords.None))
                WriteEvent(ACTOR_METHODS_LOADED, actorName, partitionName, assemblyName, methodNames);
        }

        [NonEvent]
        public void ActorMethodsUnloaded(string actorName, string partitionName, string assemblyName, List<string> methodNames)
        {
            if (base.IsEnabled(EventLevel.Informational, EventKeywords.None))
            {
                // Construct an aggregate string from the individual method names, then forward to official method
                var aggregatedMethodNames = AggregateStringList(methodNames);
                ActorMethodsUnloaded(actorName, partitionName, assemblyName, aggregatedMethodNames);
            }
        }

        [Event(ACTOR_METHODS_UNLOADED, Level = EventLevel.Informational, Keywords = EventKeywords.None)]
        public void ActorMethodsUnloaded(string actorName, string partitionName, string assemblyName, string methodNames)
        {
            if (base.IsEnabled(EventLevel.Informational, EventKeywords.None))
                WriteEvent(ACTOR_METHODS_UNLOADED, actorName, partitionName, assemblyName, methodNames);
        }

        [Event(ASSEMBLY_HAD_NO_ACTOR_METHODS, Level = EventLevel.Warning, Keywords = EventKeywords.None)]
        public void AssemblyHadNoActorMethods(string actorName, string partitionName, string assemblyName)
        {
            if (base.IsEnabled(EventLevel.Warning, EventKeywords.None))
                WriteEvent(ASSEMBLY_HAD_NO_ACTOR_METHODS, actorName, partitionName, assemblyName);
        }

        // A wrapper method to convert args from object[] to string[], and result from object to string
        [NonEvent]
        public void ActorMethodCompleted(string actorName, string partitionName, string methodName, object[] args, object result)
        {
            if (base.IsEnabled(EventLevel.Verbose, EventKeywords.None))
            {
                string resultString = "<no return value>";
                if (result != null)
                {
                    System.Collections.ICollection collection = result as System.Collections.ICollection;
                    if (collection != null)
                    {
                        resultString = result.ToString() + ", Count=" + collection.Count;
                    }
                    else
                        resultString = result.ToString();
                }
                ActorMethodCompleted(actorName, partitionName, methodName, 
                    args.Select(a => (a == null) ? "<null>" : a.ToString()).ToArray(), resultString);
            }
        }

        // A wrapper method to convert args string[] to a single string; ETW doesn't like string[].
        [NonEvent]
        public void ActorMethodCompleted(string actorName, string partitionName, string methodName, string[] args, string result)
        {
            if (base.IsEnabled(EventLevel.Verbose, EventKeywords.None))
            {
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < args.Length; i++)
                {
                    if (i > 0) sb.Append(',');
                    sb.Append(args[i]);
                }
                ActorMethodCompleted(actorName, partitionName, methodName, sb.ToString(), result);
            }
        }

        [Event(ACTOR_METHOD_COMPLETED, Level = EventLevel.Verbose, Keywords = EventKeywords.None)]
        public void ActorMethodCompleted(string actorName, string partitionName, string methodName, string args, string result)
        {
            if (base.IsEnabled(EventLevel.Verbose, EventKeywords.None))
            {
                // TODO: Create a custom WriteEvent method that takes 4 strings, so that we do not
                // hit the (params object[]) overload of WriteEvent.
                WriteEvent(ACTOR_METHOD_COMPLETED, actorName, partitionName, methodName, args, result); 
            }
        }

        // A wrapper method to convert args from object[] to string[].
        [NonEvent]
        public void ActorMethodError(string actorName, string partitionName, string methodName, object[] args,
            string exceptionType, string exceptionMessage, string exceptionCallStack)
        {
            if (base.IsEnabled(EventLevel.Error, EventKeywords.None))
            {
                ActorMethodError(actorName, partitionName, methodName, args.Select(a => a.ToString()).ToArray(),
                    exceptionType, exceptionMessage, exceptionCallStack);
            }
        }

        // A wrapper method to convert args string[] to a single string; ETW doesn't like string[].
        [NonEvent]
        public void ActorMethodError(string actorName, string partitionName, string methodName, string[] args, 
            string exceptionType, string exceptionMessage, string exceptionCallStack)
        {
            if (base.IsEnabled(EventLevel.Error, EventKeywords.None))
            {
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < args.Length; i++)
                {
                    if (i > 0) sb.Append(',');
                    sb.Append(args[i]);
                }

                ActorMethodError(actorName, partitionName, methodName, sb.ToString(), exceptionType, exceptionMessage, exceptionCallStack);
            }
        }


        [Event(ACTOR_METHOD_ERROR, Level = EventLevel.Error, Keywords = EventKeywords.None)]
        public void ActorMethodError(string actorName, string partitionName, string methodName, string args, 
            string exceptionType, string exceptionMessage, string exceptionCallStack)
        {
            if(base.IsEnabled(EventLevel.Error, EventKeywords.None))
            {
                // TODO: Create a custom WriteEvent method that takes 6 strings, so that we do not
                // hit the (params object[]) overload of WriteEvent.
                WriteEvent(ACTOR_METHOD_ERROR, actorName, partitionName, methodName, args, exceptionType, exceptionMessage, exceptionCallStack);
            }
        }

        [Event(SUBSCRIPTION_REQUEST_SENT, Level = EventLevel.Informational, Keywords = EventKeywords.None)]
        public void SubscriptionRequestSent(string actorName, string partitionName, string targetActor, string targetPartitionName, string eventType)
        {
            if (base.IsEnabled(EventLevel.Informational, EventKeywords.None))
            {
                WriteEvent(SUBSCRIPTION_REQUEST_SENT, actorName, partitionName, targetActor, targetPartitionName, eventType);
            }
        }

        [Event(SUBSCRIPTION_REQUEST_RECEIVED, Level = EventLevel.Informational, Keywords = EventKeywords.None)]
        public void SubscriptionRequestReceived(string actorName, string partitionName, string sourceActor, string eventType)
        {
            if (base.IsEnabled(EventLevel.Informational, EventKeywords.None))
            {
                WriteEvent(SUBSCRIPTION_REQUEST_RECEIVED, actorName, partitionName, sourceActor, eventType);
            }
        }

        [NonEvent]
        private string PayloadStringFromEvent(ActorEvent ae)
        {
            string payload;
            if ((ae.EventPayloadQualifiers != null) && (ae.EventPayloadQualifiers.Length > 0))
            {
                payload = String.Format("{0}({1})", ae.EventPayload, ae.EventPayloadQualifiers.Aggregate("", (acc, item) => acc + " " + item));
            }
            else if (ae.EventPayload != null) payload = ae.EventPayload;
            else payload = "<none>";
            return payload;

        }

        [NonEvent]
        public void EventPublished(string actorName, ActorEvent ae)
        {
            if (base.IsEnabled(EventLevel.Verbose, EventKeywords.None))
            {
                EventPublished(actorName, ae.EventSourcePartitionName, ae.EventType, PayloadStringFromEvent(ae));
            }
        }

        [Event(EVENT_PUBLISHED, Level=EventLevel.Verbose, Keywords=EventKeywords.None)]
        public void EventPublished(string actorName, string partitionName, string eventType, string payload)
        {
            if (base.IsEnabled(EventLevel.Verbose, EventKeywords.None))
            {
                WriteEvent(EVENT_PUBLISHED, actorName, partitionName, eventType, payload);
            }
        }

        [NonEvent]
        public void EventReceived(string actorName, string partitionName, ActorEvent ae)
        {
            if (base.IsEnabled(EventLevel.Verbose, EventKeywords.None))
            {
                EventReceived(actorName, partitionName, ae.EventSource, ae.EventSourcePartitionName, ae.EventType, PayloadStringFromEvent(ae));
            }
        }

        [Event(EVENT_RECEIVED, Level = EventLevel.Verbose, Keywords = EventKeywords.None)]
        public void EventReceived(string actorName, string partitionName, string eventSource, string eventSourcePartitionName, string eventType, string payload)
        {
            if (base.IsEnabled(EventLevel.Verbose, EventKeywords.None))
            {
                WriteEvent(EVENT_RECEIVED, actorName, partitionName, eventSource, eventSourcePartitionName, eventType, payload);
            }
        }

        /// <summary>
        /// This method is for actor methods to call to log informational messages.
        /// </summary>
        /// <param name="actorName">Pass in the name of your actor.  You can use IActorState's GetId().</param>
        /// <param name="partitionName">Pass in your partition name, if any.  You can use IActorState's PartitionName property.</param>
        /// <param name="message">Message to write to log.</param>
        [Event(USER_INFO_MESSAGE, Level = EventLevel.Informational, Keywords = EventKeywords.None)]
        public void UserInformationMessage(string actorName, string partitionName, string message)
        {
            if (base.IsEnabled(EventLevel.Informational, EventKeywords.None))
            {
                WriteEvent(USER_INFO_MESSAGE, actorName, partitionName, message);
            }
        }

        /// <summary>
        /// This method is for actor methods to log verbose messages.
        /// </summary>
        /// <param name="actorName">Pass in the name of your actor.  You can use IActorState's GetId().</param>
        /// <param name="partitionName">Pass in your partition name, if any.  You can use IActorState's PartitionName property.</param>
        /// <param name="message">Message to write to log.</param>
        [Event(USER_VERBOSE_MESSAGE, Level = EventLevel.Verbose, Keywords = EventKeywords.None)]
        public void UserVerboseMessage(string actorName, string partitionName, string message)
        {
            if (base.IsEnabled(EventLevel.Verbose, EventKeywords.None))
            {
                WriteEvent(USER_VERBOSE_MESSAGE, actorName, partitionName, message);
            }
        }

        /// <summary>
        /// Fired when an actor is first created.
        /// </summary>
        /// <param name="actorName">Pass in the name of your actor.  You can use IActorState's GetId().</param>
        /// <param name="partitionName">Pass in your partition name, if any.  You can use IActorState's PartitionName property.</param>
        [Event(ACTOR_CREATED, Level = EventLevel.Informational, Keywords = EventKeywords.None)]
        public void ActorCreated(string actorName, string partitionName)
        {
            if (base.IsEnabled(EventLevel.Informational, EventKeywords.None))
            {
                WriteEvent(ACTOR_CREATED, actorName, partitionName);
            }
        }

        /// <summary>
        /// Fired when an actor is deleted
        /// </summary>
        /// <param name="actorName">Pass in the name of your actor.  You can use IActorState's GetId().</param>
        /// <param name="partitionName">Pass in your partition name, if any.  You can use IActorState's PartitionName property.</param>
        [Event(ACTOR_DELETED, Level = EventLevel.Informational, Keywords = EventKeywords.None)]
        public void ActorDeleted(string actorName, string partitionName)
        {
            if (base.IsEnabled(EventLevel.Informational, EventKeywords.None))
            {
                WriteEvent(ACTOR_DELETED, actorName, partitionName);
            }
        }

        #region WriteEvent overloads for performance

        // The ActorMethodCompletion and ActorMethodError events both involve calling into the (params object[]) overload
        // of WriteEvent.  This is substantially slower than any of the explicit-signature overloads of WriteEvent.  We
        // can add custom WriteEvent signatures to remedy this.

        [NonEvent]
        public unsafe void WriteEvent(int eventId, string arg1, string arg2, string arg3, string arg4)
        {
            EventData* dataDesc = stackalloc EventData[4];

            if (arg1 == null) arg1 = String.Empty;
            if (arg2 == null) arg2 = String.Empty;
            if (arg3 == null) arg3 = String.Empty;
            if (arg4 == null) arg4 = String.Empty;

            // These strings should be null-terminated.  The null termination should be dropped when decoding
            // these objects.  However, .NET 4.5 did not do this appropriately.  Fixed in 4.5.1.
            fixed (char* pArg1 = arg1, pArg2 = arg2, pArg3 = arg3, pArg4 = arg4)
            {
                dataDesc[0].DataPointer = (IntPtr)pArg1;
                dataDesc[0].Size = (arg1.Length + 1) * sizeof(char);
                dataDesc[1].DataPointer = (IntPtr)pArg2;
                dataDesc[1].Size = (arg2.Length + 1) * sizeof(char);
                dataDesc[2].DataPointer = (IntPtr)pArg3;
                dataDesc[2].Size = (arg3.Length + 1) * sizeof(char);
                dataDesc[3].DataPointer = (IntPtr)pArg4;
                dataDesc[3].Size = (arg4.Length + 1) * sizeof(char);
                WriteEventCore(eventId, 4, dataDesc);
            }
        }

        [NonEvent]
        public unsafe void WriteEvent(int eventId, string arg1, string arg2, string arg3, string arg4, string arg5)
        {
            EventData* dataDesc = stackalloc EventData[5];

            if (arg1 == null) arg1 = String.Empty;
            if (arg2 == null) arg2 = String.Empty;
            if (arg3 == null) arg3 = String.Empty;
            if (arg4 == null) arg4 = String.Empty;
            if (arg5 == null) arg5 = String.Empty;

            // These strings should be null-terminated.  The null termination should be dropped when decoding
            // these objects.  However, .NET 4.5 did not do this appropriately.  Fixed in 4.5.1.
            fixed (char* pArg1 = arg1, pArg2 = arg2, pArg3 = arg3, pArg4 = arg4, pArg5 = arg5)
            {
                dataDesc[0].DataPointer = (IntPtr) pArg1;
                dataDesc[0].Size = (arg1.Length + 1) * sizeof(char);
                dataDesc[1].DataPointer = (IntPtr)pArg2;
                dataDesc[1].Size = (arg2.Length + 1) * sizeof(char);
                dataDesc[2].DataPointer = (IntPtr)pArg3;
                dataDesc[2].Size = (arg3.Length + 1) * sizeof(char);
                dataDesc[3].DataPointer = (IntPtr)pArg4;
                dataDesc[3].Size = (arg4.Length + 1) * sizeof(char);
                dataDesc[4].DataPointer = (IntPtr)pArg5;
                dataDesc[4].Size = (arg5.Length + 1) * sizeof(char);
                WriteEventCore(eventId, 5, dataDesc);
            }
        }

        [NonEvent]
        public unsafe void WriteEvent(int eventId, string arg1, string arg2, string arg3, string arg4, string arg5, string arg6)
        {
            EventData* dataDesc = stackalloc EventData[6];

            if (arg1 == null) arg1 = String.Empty;
            if (arg2 == null) arg2 = String.Empty;
            if (arg3 == null) arg3 = String.Empty;
            if (arg4 == null) arg4 = String.Empty;
            if (arg5 == null) arg5 = String.Empty;
            if (arg6 == null) arg6 = String.Empty;

            // These strings should be null-terminated.  The null termination should be dropped when decoding
            // these objects.  However, .NET 4.5 did not do this appropriately.  Fixed in 4.5.1.
            fixed (char* pArg1 = arg1, pArg2 = arg2, pArg3 = arg3, pArg4 = arg4, pArg5 = arg5, pArg6 = arg6)
            {
                dataDesc[0].DataPointer = (IntPtr)pArg1;
                dataDesc[0].Size = (arg1.Length + 1) * sizeof(char);
                dataDesc[1].DataPointer = (IntPtr)pArg2;
                dataDesc[1].Size = (arg2.Length + 1) * sizeof(char);
                dataDesc[2].DataPointer = (IntPtr)pArg3;
                dataDesc[2].Size = (arg3.Length + 1) * sizeof(char);
                dataDesc[3].DataPointer = (IntPtr)pArg4;
                dataDesc[3].Size = (arg4.Length + 1) * sizeof(char);
                dataDesc[4].DataPointer = (IntPtr)pArg5;
                dataDesc[4].Size = (arg5.Length + 1) * sizeof(char);
                dataDesc[5].DataPointer = (IntPtr)pArg6;
                dataDesc[5].Size = (arg6.Length + 1) * sizeof(char);
                WriteEventCore(eventId, 6, dataDesc);
            }
        }

        [NonEvent]
        public unsafe void WriteEvent(int eventId, string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7)
        {
            EventData* dataDesc = stackalloc EventData[7];

            if (arg1 == null) arg1 = String.Empty;
            if (arg2 == null) arg2 = String.Empty;
            if (arg3 == null) arg3 = String.Empty;
            if (arg4 == null) arg4 = String.Empty;
            if (arg5 == null) arg5 = String.Empty;
            if (arg6 == null) arg6 = String.Empty;
            if (arg7 == null) arg7 = String.Empty;

            // These strings should be null-terminated.  The null termination should be dropped when decoding
            // these objects.  However, .NET 4.5 did not do this appropriately.  Fixed in 4.5.1.
            fixed (char* pArg1 = arg1, pArg2 = arg2, pArg3 = arg3, pArg4 = arg4, pArg5 = arg5, pArg6 = arg6, pArg7 = arg7)
            {
                dataDesc[0].DataPointer = (IntPtr)pArg1;
                dataDesc[0].Size = (arg1.Length + 1) * sizeof(char);
                dataDesc[1].DataPointer = (IntPtr)pArg2;
                dataDesc[1].Size = (arg2.Length + 1) * sizeof(char);
                dataDesc[2].DataPointer = (IntPtr)pArg3;
                dataDesc[2].Size = (arg3.Length + 1) * sizeof(char);
                dataDesc[3].DataPointer = (IntPtr)pArg4;
                dataDesc[3].Size = (arg4.Length + 1) * sizeof(char);
                dataDesc[4].DataPointer = (IntPtr)pArg5;
                dataDesc[4].Size = (arg5.Length + 1) * sizeof(char);
                dataDesc[5].DataPointer = (IntPtr)pArg6;
                dataDesc[5].Size = (arg6.Length + 1) * sizeof(char);
                dataDesc[6].DataPointer = (IntPtr)pArg7;
                dataDesc[6].Size = (arg7.Length + 1) * sizeof(char);
                WriteEventCore(eventId, 7, dataDesc);
            }
        }

        #endregion WriteEvent overloads for performance

        public static readonly ActorETWEventSource Log = new ActorETWEventSource();

        /// <summary>
        /// A static method that grabs an event title from an ActorETWEventInfo
        /// </summary>
        /// <param name="eventInfo">The ActorETWEventInfo from which to glean the requested information</param>
        /// <returns>A string that captures the nature of the event</returns>
        public static string GetTitle(ActorETWEventInfo eventInfo)
        {
          Contract.Requires(eventInfo != null);

            var timeString = DateTime.FromBinary(eventInfo.Timestamp).ToString("hh:mm:ss.fff");
            var id = eventInfo.EventId;
            switch (id)
            {
                case ADD_ASSEMBLY: return String.Format("{0}: AssemblyAdded({1},{2})", timeString, eventInfo.GetTypedPayloadItem<string>(0), eventInfo.GetTypedPayloadItem<string>(1));
                case ASSEMBLY_LOAD_FAILURE: return String.Format("{0}: AssemblyLoadFailure", timeString);
                case ACTOR_METHOD_SIGNATURE_FAILURE: return String.Format("{0}: ActorMethodSignatureFailure", timeString);
                case ACTOR_METHODS_LOADED: return String.Format("{0}: ActorMethodsLoaded", timeString);
                case ACTOR_METHOD_COMPLETED: return String.Format("{0}: ActorMethodCompleted({1})", timeString, eventInfo.GetTypedPayloadItem<string>(0));
                case ACTOR_METHOD_ERROR: return String.Format("{0}: ActorMethodError({1})", timeString, eventInfo.GetTypedPayloadItem<string>(0));
                case ASSEMBLY_HAD_NO_ACTOR_METHODS: return String.Format("{0}: AssemblyHadNoActorMethods", timeString);
                case SUBSCRIPTION_REQUEST_SENT: return String.Format("{0}: SubscriptionRequestSent({1})", timeString, eventInfo.GetTypedPayloadItem<string>(2));
                case SUBSCRIPTION_REQUEST_RECEIVED: return String.Format("{0}: SubscriptionRequestReceived({1})", timeString, eventInfo.GetTypedPayloadItem<string>(1));
                case EVENT_PUBLISHED: return String.Format("{0}: EventPublished({1})", timeString, eventInfo.GetTypedPayloadItem<string>(0));
                case EVENT_RECEIVED: return String.Format("{0}: EventReceived({1})", timeString, eventInfo.GetTypedPayloadItem<string>(2));
                case DUPLICATE_ASSEMBLY_RECEIVED: return String.Format("{0}: DuplicateAssembly({1})", timeString, eventInfo.GetTypedPayloadItem<string>(0));
                case ACTOR_METHODS_UNLOADED: return String.Format("{0}: ActorMethodsUnloaded", timeString);
                case USER_INFO_MESSAGE: return String.Format("{0}: User Informational Message", timeString);
                case USER_VERBOSE_MESSAGE: return String.Format("{0}: User Verbose Message", timeString);
                case ACTOR_CREATED: return String.Format("{0}: ActorCreated", timeString);
                case ACTOR_DELETED: return String.Format("{0}: ActorDeleted", timeString);
                default:
                    Debug.Assert(false, "Unknown ActorETWEventInfo in GetTitle");
                    return "UNKNOWN!!";
            }
        }

        /// <summary>
        /// A static method to grab details, in the form of strings, from an ActorETWEventInfo object.
        /// </summary>
        /// <param name="eventInfo">The ActorEventETWInfo from which to glean the details</param>
        /// <returns>An array of detail strings, or null if the eventId in args is unrecognized</returns>
        public static string[] GetDetails(ActorETWEventInfo eventInfo)
        {
            Contract.Requires(eventInfo != null);

            var id = eventInfo.EventId;
            
            switch (id)
            {
                case ADD_ASSEMBLY: 
                    return new string[0];
                case DUPLICATE_ASSEMBLY_RECEIVED:
                    return new string[0];
                case ASSEMBLY_LOAD_FAILURE: 
                    return new string[] {
                        String.Format("assemblyName={0}, exceptionMessage={1}", 
                            eventInfo.GetTypedPayloadItem<string>(0),
                            eventInfo.GetTypedPayloadItem<string>(1))};
                case ACTOR_METHOD_SIGNATURE_FAILURE: 
                    return new string[] {
                        String.Format("assemblyName={0}, methodName={1}",
                            eventInfo.GetTypedPayloadItem<string>(0),
                            eventInfo.GetTypedPayloadItem<string>(1))};
                case ACTOR_METHODS_LOADED:
                    String methodNames = eventInfo.GetTypedPayloadItem<string>(1);
                    const int lineLength = 130;
                    if (methodNames.Length > lineLength)
                    {
                        var methodLines = new List<String>();
                        var parts = methodNames.Split(',');
                        StringBuilder currentLine = new StringBuilder();
                        int firstLineExtraSpace = 45;
                        for (int i = 0; i < parts.Length; i++)
                        {
                            if (currentLine.Length + parts[i].Length + firstLineExtraSpace > lineLength)
                            {
                                methodLines.Add(currentLine.ToString());
                                currentLine.Clear();
                                firstLineExtraSpace = 0;
                            }
                            currentLine.Append(parts[i]);
                            if (i < parts.Length - 1)
                                currentLine.Append(", ");
                            else
                                methodLines.Add(currentLine.ToString());
                        }
                        String[] retVal = new String[methodLines.Count];
                        retVal[0] = String.Format("assemblyName={0}, methodNames={1}",
                            eventInfo.GetTypedPayloadItem<string>(0),
                            methodLines[0]);
                        for (int i = 1; i < methodLines.Count; i++)
                            retVal[i] = methodLines[i];
                        return retVal;
                    }
                    else
                    {
                        return new string[] { 
                        String.Format("assemblyName={0}, methodNames={1}",
                            eventInfo.GetTypedPayloadItem<string>(0),
                            eventInfo.GetTypedPayloadItem<string>(1))};
                    }
                case ACTOR_METHODS_UNLOADED:
                    return new string[] { 
                        String.Format("assemblyName={0}, methodNames={1}",
                            eventInfo.GetTypedPayloadItem<string>(0),
                            eventInfo.GetTypedPayloadItem<string>(1))};
                case ACTOR_METHOD_COMPLETED: 
                    return new string[] {
                        String.Format("args={0}, result={1}",
                            eventInfo.GetTypedPayloadItem<string>(1),
                            eventInfo.GetTypedPayloadItem<string>(2))};
                case ACTOR_METHOD_ERROR:
                    var stackTrace = eventInfo.GetTypedPayloadItem<string>(4);
                    //TODO: Cache this \r \n array so that we're not creating it every time.
                    var stackTraceParts = (stackTrace == null) ? null : stackTrace.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
                    var totalParts = (stackTraceParts == null) ? 2 : stackTraceParts.Length + 3;
                    var result = new string[totalParts];
                    result[0] = String.Format("args={0}",
                            eventInfo.GetTypedPayloadItem<string>(1));
                    result[1] = String.Format("exceptionType={0}, exceptionMessage={1}",
                            eventInfo.GetTypedPayloadItem<string>(2),
                            eventInfo.GetTypedPayloadItem<string>(3));
                    if (stackTraceParts != null)
                    {
                        result[2] = "Stack Trace:";
                        for (int i = 0; i < stackTraceParts.Length; i++)
                        {
                            Contract.Assert(result.Length == stackTraceParts.Length + 3, "Let's help cccheck");  // Helps static checker discover what it can prove.
                            result[i + 3] = stackTraceParts[i];
                        }
                    }
                    return result;
                case ASSEMBLY_HAD_NO_ACTOR_METHODS:
                    return new string[] {
                        String.Format("assemblyName={0}",
                            eventInfo.GetTypedPayloadItem<string>(0)),
                        "Actor methods must be defined on public types, and the methods must be public and static." };
                case SUBSCRIPTION_REQUEST_SENT:
                    var partitionName = eventInfo.GetTypedPayloadItem<string>(1);
                    bool hasPartitionName = !String.IsNullOrEmpty(partitionName);
                    var resultSub = new string[(hasPartitionName) ? 2 : 1];
                    resultSub[0] = String.Format("targetActor={0}", eventInfo.GetTypedPayloadItem<string>(0));
                    if (hasPartitionName)
                        resultSub[1] = String.Format("targetPartitionName={0}", eventInfo.GetTypedPayloadItem<string>(1));
                    return resultSub;
                case SUBSCRIPTION_REQUEST_RECEIVED:
                    return new string[] {
                        String.Format("sourceActor={0}", eventInfo.GetTypedPayloadItem<string>(0))};
                case EVENT_PUBLISHED:
                    return new string[] {
                        String.Format("payload={0}", eventInfo.GetTypedPayloadItem<string>(1)) };
                case EVENT_RECEIVED:
                    return new string[] {
                        String.Format("eventSource={0}", eventInfo.GetTypedPayloadItem<string>(0)),
                        String.Format("eventSourcePartitionName={0}", eventInfo.GetTypedPayloadItem<string>(1)),
                        String.Format("payload={0}", eventInfo.GetTypedPayloadItem<string>(3))};
                case USER_INFO_MESSAGE:
                case USER_VERBOSE_MESSAGE:
                    return new string[] {
                        String.Format("message={0}", eventInfo.GetTypedPayloadItem<string>(0)) };
                case ACTOR_CREATED:
                case ACTOR_DELETED:
                    return new string[0];
                default:
                    Debug.Assert(false, "Unknown ActorETWEventInfo in GetDetails");
                    return null;
            }
        }
    }

    // We need this class because EventWrittenEventArgs has no constructor, and therefore we have
    // a hard time deserializing it.  So all EventWrittenEventArgs objects are converted internally
    // to ActorETWEventInfo objects before being published/serialized.
    [Serializable]
    public class ActorETWEventInfo
    {
        public EventLevel Level { get; private set; }
        public int EventId { get; private set; }
        public List<string> Payload { get; private set; }
        public string PartitionName { get; private set; }
        public long Timestamp { get; private set; }

        private static bool EqualsActorNameIgnoreTerminatingZero(String actorNameFromPayload, String intendedTargetActor)
        {
            // @TODO: After .NET 4.5.1. has been released, replace this with a call to String.Equals, using an ordinal comparison.
            // The problem is that the actor name from the payload may contain a terminating null, incorrectly.
            // This throws off our string equality checks.  We have to either allocate a new string to use Equals,
            // or fall back to CompareOrdinal.  Let's avoid an allocation.
            int len = actorNameFromPayload.Length;
            if (len > 0 && actorNameFromPayload[len - 1] == '\0')
                len--;
            if (len != intendedTargetActor.Length)
                return false;
            return String.CompareOrdinal(actorNameFromPayload, 0, intendedTargetActor, 0, len) == 0;
        }

        // Static method to convert an EventWrittenEventArgs object into an ActorETWEventInfo object.
        // Will only return a non-null object if the args passed in pertain to the intendedTarget.
        //
        // Note that the timestamp is stripped from the payload, and that payload objects are
        // converted to JSON strings.
        public static ActorETWEventInfo FromEventWrittenEventArgs(EventWrittenEventArgs args, string intendedTargetActor)
        {
            Contract.Requires(args != null);
            Contract.Requires(args.Payload != null);
            Contract.Requires(intendedTargetActor != null);

            // TODO: When .NET 4.5.1 has been out on Windows Update for a while, remove the RemoveTerminatingNull method.
            // Also, remove the EqualsActorNameIgnoreTerminatingZero method.
            var targetActor = RemoveTerminatingNull((string)args.Payload[0]);

            //if (String.Equals(targetActor, intendedTargetActor, StringComparison.Ordinal))
            if (EqualsActorNameIgnoreTerminatingZero(targetActor, intendedTargetActor))
            {
                ActorETWEventInfo actorArgs = new ActorETWEventInfo();
                actorArgs.PartitionName = RemoveTerminatingNull((string) args.Payload[1]);
                actorArgs.Level = args.Level;
                actorArgs.EventId = args.EventId;
                // Remove the target actor name and target partition name from the payload
                actorArgs.Payload = args.Payload.Skip(2).Select(item => JsonConvert.SerializeObject(RemoveTerminatingNullIfString(item))).ToList();
                actorArgs.Timestamp = DateTime.UtcNow.ToBinary();
                return actorArgs;
            }
            else
            {
                return null;
            }
        }

        // .NET 4.5 had a bug where when reading events, listeners would get back strings
        // with an extra '\0' on the end.  This is fixed in .NET 4.5.1.
        private static String RemoveTerminatingNull(String str)
        {
            int len = str.Length;
            if (len > 0 && str[len - 1] == '\0')
                return str.Substring(0, len - 1);
            return str;
        }

        private static Object RemoveTerminatingNullIfString(Object obj)
        {
            String str = obj as String;
            if (str != null)
                return RemoveTerminatingNull(str);
            return obj;
        }

        // Convert a payload JSON string into a strongly typed item.
        public T GetTypedPayloadItem<T>(int index)
        {
            Contract.Requires(index >= 0);
            Contract.Requires(index < Payload.Count);

            return JsonConvert.DeserializeObject<T>(Payload[index]);
        }

        // Provide a fast JSON deserializer for ActorETWEventInfo
        public static ActorETWEventInfo FromJson(string json)
        {
            Contract.Requires(json != null);

            var sr = new StringReader(json);
            var reader = new JsonTextReader(sr);
            var args = new ActorETWEventInfo();
            while (reader.Read())
            {
                if (reader.TokenType == JsonToken.PropertyName)
                {
                    var name = (string)reader.Value;
                    if (name.Equals("Level"))
                    {
                        reader.Read();
                        var value = (EventLevel)(long)reader.Value;
                        Contract.Assume(Enum.IsDefined(typeof(EventLevel), value), "Undefined EventLevel value.");
                        args.Level = value;
                    }
                    else if (name.Equals("EventId"))
                    {
                        reader.Read();
                        args.EventId = (int)(long)reader.Value;
                    }
                    else if (name.Equals("Timestamp"))
                    {
                        reader.Read();
                        args.Timestamp = (long)reader.Value;
                    }
                    else if (name.Equals("Payload"))
                    {
                        reader.Read(); // This token will either be "null" or a begin-array token
                        if (reader.TokenType != JsonToken.Null) // it's a begin-array
                        {
                            var argList = new List<string>();
                            reader.Read(); // Skip past the begin-array token
                            while (reader.TokenType != JsonToken.EndArray)
                            {
                                argList.Add((string)reader.Value);
                                reader.Read();
                            }
                            args.Payload = argList;
                        }
                    }
                }
            }

            return args;
        }

        // Provide a fast JSON serializer for ActorETWEventInfo
        public static string ToJson(ActorETWEventInfo args)
        {
            Contract.Requires(args != null);

            var sw = new StringWriter();
            var writer = new JsonTextWriter(sw);

            writer.WriteStartObject();

            writer.WritePropertyName("EventId"); writer.WriteValue(args.EventId);
            writer.WritePropertyName("Level"); writer.WriteValue((long)args.Level);
            writer.WritePropertyName("Timestamp"); writer.WriteValue(args.Timestamp);
            if(args.Payload != null)
            {
                writer.WritePropertyName("Payload");
                writer.WriteStartArray();
                foreach (var arg in args.Payload) writer.WriteValue(arg);
                writer.WriteEndArray();
            }

            writer.WriteEndObject();

            return sw.ToString();
        }


    }
}
